package ai.wprolog.essentials;

import java.util.Hashtable;
import java.util.Stack;

import util.essential.SourceGetter;

/**
 * An engine is what executes prolog queries.
 * 
 * @author Michael Winikoff
 */
public class Engine {
	/**
	 * The stack holds choicepoints and a list of variables which need to be
	 * un-bound upon backtracking.
	 */
	private Stack stack;
	/** We use a hashtable to store the program */
	private Hashtable<String,TermList> db;
	private TermList goal;
	private Term call;
	/** This governs whether tracing is done */
	public static boolean trace = false;
	/** Used to time how long queries take */
	private long time;

	/** A bookmark to the fail predicate */
	private TermList failgoal;

	/**
	 * @param prog
	 *            An initial program - this will be extended
	 * @param g
	 *            The query to be executed
	 */
	public Engine(Term g, Hashtable<String,TermList> prog) {
		Term t, t2;
		TermList tl;

		stack = new Stack();
		goal = new TermList(g, null);
		call = g;

		// enable underscore as first character of term
		Term.internalparse = true;

		try {
			db = ParseString
					.consult(
							("eq(X,X). fail :- eq(c,d). print(X) :- _print(X)."
									+ "if(X,Y,Z) :- once(wprologtest(X,R)) , wprologcase(R,Y,Z)."
									+ "wprologtest(X,yes) :- call(X). wprologtest(X,no). "
									+ "wprologcase(yes,X,Y) :- call(X). "
									+ "wprologcase(no,X,Y) :- call(Y)."
									+ "not(X) :- if(X,fail,true). or(X,Y) :- call(X). "
									+ "or(X,Y) :- call(Y)."
									+ "true. call(X) :- _call(X). "
									+ "nl :- _nl. once(X) :- _onceenter , call(X) , _onceleave."),
							prog, null);
			ParseString.resolve(db);
		} catch (Exception e) {
			SourceGetter.out.errorMsg("Engine.init", "Can't parse default program!");
		}

		Term.internalparse = false;

		goal.resolve(db);

		failgoal = new TermList(new Term("fail", 0), null);
		failgoal.resolve(db);

		//setPriority(2);

		time = System.currentTimeMillis();

	}

	final public void dump(int clausenum) {
		if (trace) {
			SourceGetter.out.debug("Goal: " + goal + " clausenum = " + clausenum);
		}
	}
	
	/** run does the actual work. */
	final public String run() {
		boolean found;
		String func;
		int arity, clausenum;
		TermList clause, nextclause, ts1, ts2;
		Term t;
		Object o;
		ChoicePoint cp;
		Term vars[];

		clausenum = 1;

		while (true) {
			if (goal == null) {
				Runtime r = Runtime.getRuntime();
				r.gc();
				long totalmem = r.totalMemory();
				long currentmem = r.freeMemory();
				long percent = (100 * (totalmem - currentmem)) / totalmem;
				totalmem = (long) (((float) totalmem) / 1000000.0);
				/*return ("Yes: " + call + "\n(approx. "
						+ (System.currentTimeMillis() - time)
						+ " ms., memory used: " + percent + "% out of "
						+ totalmem + " Mb)");*/
				// this.suspend();
				// return;
				return ("Yes: " + call);
			}

			if (goal.term == null) {
				SourceGetter.out.errorMsg("Engine.run", "goal.term is null!");
			}

			func = goal.term.getfunctor();
			arity = goal.term.getarity(); // is this needed?
			dump(clausenum);

			if (func.charAt(0) != '_') {
				// ie the goal is not a system predicate AAA
				// if there is an alternative clause push choicepoint:
				if (goal.numclauses > clausenum)
					stack.push(new ChoicePoint(clausenum + 1, goal));

				if (clausenum > goal.numclauses) {
					clause = failgoal;
					SourceGetter.out.message("Diagnostic: ",func + "/" + arity + " undefined!");
					clause = new TermList(new Term("fail", 0), goal);
					clause.resolve(db);
				} else
					clause = goal.definer[clausenum];

				clausenum = 1; // reset ...
				// check unification ...
				vars = new Term[300]; // XXX arbitrary limit
				if (clause.term.refresh(vars).unify(goal.term, stack)) {
					clause = clause.next;

					// refresh clause -- need to also copy definer
					if (clause != null) {
						ts1 = new TermList(clause.term.refresh(vars), null,
								clause);
						ts2 = ts1;
						clause = clause.next;

						while (clause != null) {
							ts1.next = new TermList(clause.term.refresh(vars),
									null, clause);
							ts1 = ts1.next;
							clause = clause.next;
						}

						// splice together refreshed clause and other goals
						ts1.next = goal.next;
						goal = ts2;

						// For GC purposes drop references to data that is not
						// needed
						t = null;
						ts1 = null;
						ts2 = null;
						vars = null;
						clause = null;
						nextclause = null;
						func = null;

					} else { // matching against fact ...
						goal = goal.next;
					}
				} else { // unify failed - backtrack ...

					goal = goal.next;
					found = false;
					while (!stack.empty()) {
						o = stack.pop();

						if (o instanceof Term) {
							t = (Term) o;
							t.unbind();
						} else if (o instanceof ChoicePoint) {
							cp = (ChoicePoint) o;
							goal = cp.goal;
							clausenum = cp.clausenum;
							found = true;
							break;
						} // else if integer .. iterative deepening
						// not implemented yet
					}

					// stack is empty and we have not found a choice
					// point ... fail ...

					if (!found) {
						return "No."; // IO.result("No.");
						// this.suspend();
						// return;
					}
				}

			} // AAA
			else if (func.equals("_print") & arity == 1) {
				SourceGetter.out.message("Prolog: ",goal.term.getarg(0).toString());
				goal = goal.next;
			} else if (func.equals("_nl") & arity == 0) {
				SourceGetter.out.message("Prolog: ","\n");
				goal = goal.next;
			} else if (func.equals("_call") & arity == 1) {
				// System.out.println(goal.term);
				// System.out.println(goal.term.getarg(0));
				TermList templist = new TermList(goal.term.getarg(0), null);
				templist.resolve(db);
				templist.next = goal.next;
				goal = templist;
			}
			// The next two together implement once/1
			else if (func.equals("_onceenter") & arity == 0) {
				stack.push(new OnceMark());
				goal = goal.next;
			} else if (func.equals("_onceleave") & arity == 0) {
				// find mark, remove mark and all choicepoints above it.
				Stack tempstack = new Stack();
				o = stack.pop();
				while (!(o instanceof OnceMark)) {
					// forget choicepoints
					if (!(o instanceof ChoicePoint))
						tempstack.push(o);
					o = stack.pop();
				}

				while (!(tempstack.empty()))
					stack.push(tempstack.pop());

				tempstack = null;
				goal = goal.next;
			} else {
				SourceGetter.out.debug("Unknown builtin: " + func + "/" + arity);
				goal = goal.next;
			}

		} // while
	} // run
	public String more() {
		goal = new TermList(new Term("fail", 0), goal);
		goal.resolve(db);
		time = System.currentTimeMillis();
		return this.run();
	}

} // Engine